package cn.whitecao.agent.core.loaders;




import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.io.IoUtil;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * 外部jar加载器 没用JDK的原生SPI
 * @author Bashful Huang
 * @since 2020-09-08
 **/
@Slf4j
public class ExternalJarClassLoader extends ClassLoader {

    private final List<File> jarFiles=new ArrayList<>();
    /**
     * 路径下的所有jar
     * @param path jar的路径
     */
    public ExternalJarClassLoader(String path) {
        this.jarFiles.addAll(getJarFilesFromPath(path));
    }

    /**
     * 指定的jar
     * @param jarFile 指定的jar
     */
    public ExternalJarClassLoader(File jarFile) {
        this(Collections.singletonList(jarFile));
    }

    public ExternalJarClassLoader(List<File> jars) {
        for (File jarFile:jars) {
            if (!jarFile.isFile()||!jarFile.getName().endsWith(".jar")){
                throw new IllegalArgumentException("传入正确的jar");
            }
            jarFiles.add(jarFile);
        }
    }



    /**
     *
     * @param name 文件名
     * @return URL Enumeration
     */
    @Override
    protected Enumeration<URL> findResources(String name)  {
        List<URL> matchedUrl=new ArrayList<>();
        for (File file : this.jarFiles) {
            try {
                JarFile jarFile = new JarFile(file);
                if (jarFile.getJarEntry(name)!=null){
                    matchedUrl.add(new URL("jar:file:" + file.getAbsolutePath() + "!/" + name));
                }
            } catch (IOException e) {
                //如果只是个.jar后缀文件非java的jar包的话是会异常的
                log.info("加载" + file.getName()+"失败");
            }
        }

        Iterator<URL> urlIterator = matchedUrl.iterator();
        return new Enumeration<URL>() {
            @Override
            public boolean hasMoreElements() {
                return urlIterator.hasNext();
            }
            @Override
            public URL nextElement() {
                return urlIterator.next();
            }
        };
    }

    /**
     * 重写这个方法是因为  {@link net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader#locate(String)}
     * 中有调用classLoader.getResourceAsStream.根据双亲委派,所有的父加载器都找不到的话 就会用本加载器
     *  getResourceAsStream会调用findResource来定位这个类的URL,故重写之
     * 不知道出于何种考虑,byte buddy把这个异常给吞掉了,故插件的切面一直失败
     *
     * @param name 类名
     * @return 类的URL
     */
    @Override
    protected URL findResource(String name) {

        for (File file:jarFiles) {
            try {
                JarFile jarFile = new JarFile(file);
                if (jarFile.getJarEntry(name)!=null){
                    String classAbsolutePath = "jar:file:" + file.getAbsolutePath() + "!/" + name;
                    return new URL(classAbsolutePath);
                }
            } catch (IOException e) {
                log.info("无法加载"+file.getAbsolutePath());
            }
        }
        return null;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/") + ".class";
        for (File file:jarFiles) {
            try {
                JarFile jarFile = new JarFile(file);
                if (jarFile.getJarEntry(classPath)!=null){
                    String classAbsolutePath = "jar:file:" + file.getAbsolutePath() + "!/" + classPath;
                    byte[] classBytes = readClass(classAbsolutePath);
                    return super.defineClass(name, classBytes, 0, classBytes.length);
                }
            } catch (IOException e) {
                log.info("无法加载"+file.getAbsolutePath());
            }
        }
        throw new ClassNotFoundException("无法找到类 "+name);
    }

    private List<File> getJarFilesFromPath(String path){
        List<File> result=new ArrayList<>();
        File jarDirectory = new File(path);
        //路径不存在这个也会返回false
        if (jarDirectory.isDirectory()){
            File[] fileArray = jarDirectory.listFiles();
            if (fileArray!=null){
                List<File> fileList = Arrays.asList(fileArray);
                result = fileList.stream()
                        //过滤掉路径和非jar文件
                        .filter(file -> file.isFile() && file.getName().endsWith(".jar"))
                        .collect(Collectors.toList());
            }
        }
        return result;
    }

    private byte[] readClass(String classPath) throws IOException {
        URL url = new URL(classPath);
        return IoUtil.readBytes(url.openStream());
    }
}